package com.kongjs.application.aspect;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.kongjs.application.annotation.OperationLogging;
import com.kongjs.application.model.entity.OperationLog;
import com.kongjs.application.service.IOperationLogService;
import com.kongjs.application.util.IpUtil;
import com.kongjs.common.mybatis.tenant.TenantContextHolder;
import com.kongjs.common.mybatis.user.UserContextHolder;
import com.kongjs.common.rest.util.JsonUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.concurrent.Executor;

@Slf4j
@Aspect
@Component
public class OperationLogAspect {
    @Resource
    private Executor applicationTaskExecutor;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private IOperationLogService operationLogService;

    @Around("@within(com.kongjs.application.annotation.OperationLogging) || @annotation(com.kongjs.application.annotation.OperationLogging)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        OperationLogging annotation = method.getAnnotation(OperationLogging.class);
        if (ObjectUtils.isEmpty(annotation)) {
            return point.proceed();
        }
        long start = System.currentTimeMillis();
        OperationLog operationLog = new OperationLog();
        operationLog.setOperationTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(start), ZoneId.systemDefault()));
        Object[] args = point.getArgs();
        Object proceed = null;
        try {
            operationLog.setOperationResult("success");
            proceed = point.proceed();
            return proceed;
        } catch (Exception e) {
            operationLog.setOperationException(ExceptionUtil.stacktraceToString(e));
            operationLog.setOperationResult("error");
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            long ms = end - start;
            operationLog.setExecutionTime(ms);
            operationLog.setOperationMethod(method.toString());
            SecurityContext context = SecurityContextHolder.getContext();
            Authentication authentication = context.getAuthentication();
            WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
            String remoteAddress = details.getRemoteAddress();
            operationLog.setUserName(authentication.getName());
            operationLog.setOperationIpaddr(remoteAddress);
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                operationLog.setOperationPath(request.getRequestURI());
            }
            try {
                String tenantId = TenantContextHolder.getTenantId();
                String userId = UserContextHolder.getUserId();
                if (StringUtils.hasText(tenantId)) {
                    Long id = NumberUtils.parseNumber(tenantId, Long.class);
                    operationLog.setTenantId(id);
                }
                if (StringUtils.hasText(userId)) {
                    Long id = NumberUtils.parseNumber(userId, Long.class);
                    operationLog.setUserId(id);
                }
            } catch (Exception e) {
                log.error("记录操作日志异常", e);
            }
            saveLog(operationLog, annotation, args, proceed);
        }
    }

    private void saveLog(final OperationLog operationLog, final OperationLogging annotation, final Object[] args, final Object proceed) {
        // 异步保存
        applicationTaskExecutor.execute(() -> {
            try {
                operationLog.setOperationParam(JsonUtils.toJson(args));
                if (annotation.recordResponse()) {
                    if (!ObjectUtils.isEmpty(proceed)){
                        operationLog.setOperationMessage(JsonUtils.toJson(proceed));
                    }
                }
                operationLog.setOperationName(annotation.operationName());
                operationLog.setOperationType(annotation.operationType());

                String operationIpaddr = operationLog.getOperationIpaddr();
                if (StringUtils.hasText(operationIpaddr)) {
                    String region = IpUtil.getRegion(operationIpaddr);
                    operationLog.setOperationIpinfo(region);
                }
                // 成功提交
                transactionTemplate.executeWithoutResult((status) -> {
                    operationLogService.save(operationLog);
                });
            } catch (Exception e) {
                log.error("记录操作日志异常", e);
            }
        });
    }
}
